package de.unidue.langtech.teaching.pp.example;

import java.io.File;
import java.io.IOException;
import java.util.*;

import javax.xml.parsers.*;

import org.w3c.dom.*;
import org.apache.uima.UimaContext;
import org.apache.uima.collection.CollectionException;
import org.apache.uima.fit.component.JCasCollectionReader_ImplBase;
import org.apache.uima.fit.descriptor.ConfigurationParameter;
import org.apache.uima.jcas.JCas;
import org.apache.uima.resource.ResourceInitializationException;
import org.apache.uima.util.Progress;
import org.apache.uima.util.ProgressImpl;
import de.unidue.langtech.teaching.pp.type.BaseTime;
import de.unidue.langtech.teaching.pp.type.TimexEvent;
import org.xml.sax.SAXException;

/**
 * @author Inui
 */
public class TimeReader extends JCasCollectionReader_ImplBase {

	public static final String PARAM_INPUT_DIRECTORY = "InputDirectory";
	@ConfigurationParameter(name = PARAM_INPUT_DIRECTORY, mandatory = true)
	private File inputDirectory;
	private Queue<File> files = new LinkedList<File>();
	private int numberOfDocuments = 0;

	  /**
     * Initializes the reader with the UIMA context. Additionally, determines all input files and puts them into
     * member variable {@link #files}.
     *
     * @param context the UIMA context.
     * @throws ResourceInitializationException if initialization fails.
     */
    public void initialize(UimaContext context) throws ResourceInitializationException {
        super.initialize(context);
        try {
            fileList(inputDirectory);
        } catch (IOException e) {
            throw new ResourceInitializationException(e);
        }
    }

	/**
	 * Fills the given JCas with the information from the next input file.
	 * 
	 * @param jcas
	 *            the JCas to fill. //Methodenparameter jcas, Klasse JCas
	 * @throws IOException
	 *             if file IO fails.
	 * @throws CollectionException
	 *             if anything else fails.
	 */

	@Override
	public void getNext(JCas jcas) throws IOException, CollectionException {
		// get (and remove) next file from queue
		File file = files.poll();

		try {
			/*
			 * source:
			 * http://stackoverflow.com/questions/20744281/parsing-xml-file
			 * -from-resources
			 * 
			 * XML parser analyzes the XML file including all information
			 * therein
			 */
			DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder documentBuilder = documentBuilderFactory
					.newDocumentBuilder();
			Document document = documentBuilder.parse(file);
			document.getDocumentElement().normalize();

			NodeList allTextNodes = document.getElementsByTagName("TEXT");
			Node textNode = allTextNodes.item(0);
			String text = textNode.getTextContent();
			// text goes to the jcas
			jcas.setDocumentText(text);

			// go to the DTC tag and then to the TIMEX3 element
			NodeList dctNodes = document.getDocumentElement()
					.getElementsByTagName("DCT");
			NodeList baseTimexNodes = ((Element) dctNodes.item(0))
					.getElementsByTagName("TIMEX3");
			Node baseNode = baseTimexNodes.item(0);

			// http://www.w3.org/2003/01/dom2-javadoc/org/w3c/dom/NamedNodeMap.html
			// now get the 'value' attribute from the TIMEX3 element
			NamedNodeMap documentTimeNodeMap = baseNode.getAttributes();
			Node basetimeAttribute = documentTimeNodeMap.getNamedItem("value");
			// the extracted value is the basetime of the document. We need it
			// as a String.
			String basetime = basetimeAttribute.getTextContent();

			// the DOCID tag contains the filename. Get it in the same way as
			// above
			NodeList fileNameList = document.getElementsByTagName("DOCID");
			Node fileNode = fileNameList.item(0);
			String fileName = fileNode.getTextContent();

			System.out.println("======================================");
			System.out.println("File: '" + fileName + "'");
			// optional: give also the text
			// System.out.println("Document is: " + jcas.getDocumentText());
			System.out.println("--------------------------");
			System.out.println("Basetime: " + basetime);

			// source: projectVestergaard
			// give the stored attributes to the jcas
			BaseTime baseTime = new BaseTime(jcas);
			baseTime.setValue(basetime);
			baseTime.setFileName(fileName);
			baseTime.addToIndexes();
			baseTime.setBasetimeYear(Integer.parseInt(basetime.substring(0, 4)));
			baseTime.setBasetimeMonth(Integer.parseInt(basetime.substring(5, 7)));
			baseTime.setBasetimeDay(Integer.parseInt(basetime.substring(8, 10)));

			// get the <TIMEX3> tags in the <TEXT> tag
			NodeList textNodes = document.getDocumentElement()
					.getElementsByTagName("TEXT");
			// creating a nodeList with all timex events. while the nodeList is
			// not empty, you get the next timex event
			NodeList timexNodes = ((Element) textNodes.item(0))
					.getElementsByTagName("TIMEX3");
			// get the number of timex events if there is at least one event
			if (timexNodes.getLength() > 0) {

				for (int i = 0; i < timexNodes.getLength(); i++) {
					Node timexNode = timexNodes.item(i);
					NamedNodeMap timexNodeMap = timexNode.getAttributes();

					// get the text in the <TIMEX3> tag
					String timexText = timexNode.getTextContent();

					// the extracted attributes are needed as Strings
					String timexValue = timexNodeMap.getNamedItem("value")
							.getTextContent();
					String timexType = timexNodeMap.getNamedItem("type")
							.getTextContent();

					// to jcas
					TimexEvent timexEvent = new TimexEvent(jcas);
					timexEvent.setGoldValue(timexValue);
					timexEvent.setTimexType(timexType);
					timexEvent.setTimexText(timexText);
					timexEvent.addToIndexes();
				}
			}
		} catch (ParserConfigurationException e) {
			// wrap exception from XML parser and pass up to UIMA framework
			throw new IOException("exception while reading file "
					+ file.getAbsolutePath(), e);
		} catch (SAXException e) {
			// wrap exception from XML parser and pass up to UIMA framework
			throw new IOException("exception while reading file "
					+ file.getAbsolutePath(), e);
		}
	}

	/**
	 * Returns <code>true</code>, if more input files are available.
	 * 
	 * @return <code>true</code>, if more input files are available.
	 * @throws IOException
	 *             if file IO fails.
	 * @throws CollectionException
	 *             if anything else fails.
	 */
	public boolean hasNext() throws IOException, CollectionException {
		return !files.isEmpty();
	}

	/**
	 * Returns progress of the pipeline, i.e., percentage of
	 * files processed.
	 * source: HeidelTime 
	 */
	public Progress[] getProgress() {
		// files.size() is the number of remaining documents
		// progress is processed documents (numberOfDocuments - files.size()) in
		// relation to total number of documents
		return new Progress[] { new ProgressImpl(numberOfDocuments
				- files.size(), numberOfDocuments, "numberOfDocuments") };
	}

	 /**
     * Fills the {@link #files list of files} with all available input files.
     *
     * @param directory directory to search in for input files.
     * @throws IOException if file IO fais.
     */
    private void fileList(File directory) throws IOException {
        ArrayList<File> fileList = new ArrayList<File>();

        // checking the directory and the file for there consistency
        if (directory != null && directory.exists() && directory.isDirectory()) {
            fileList.addAll(Arrays.asList(directory.listFiles()));
        } else {
            if (directory == null) {
                throw new IOException("paramter 'directory' must not be 'null'");
            } else if (!directory.exists()) {
                throw new IOException("directory " + directory.getAbsolutePath() + " does not exist");
            } else {
                throw new IOException("file " + directory.getAbsolutePath() + " is not a directory");
            }
        }

        for (File file : fileList) {
            if (file.exists() && file.isFile() && file.canRead()) {
                files.add(file);
            //if the file doesn't exist, isn't a file or can't be read, give an information to the user
            } else {
                System.out.println("file " + file.getPath() + " cannot be processed.");
            }
        }
        //numberOfDocuments is the same as the number of files which are saved in files.size
        numberOfDocuments = files.size();
    }
}
